////////////////////////////////////////////////////////////////////////////////
//
//  FLYING KIWI BV
//  Copyright 2010 Flying Kiwi BV
//  All Rights Reserved.
//
//  NOTICE: Flying Kiwi permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package plantool.modules.users {
	//--------------------------------------------------------------------------
	//  Imports
	//--------------------------------------------------------------------------
	import flash.events.Event;
	
	import caurina.transitions.Tweener;
	
	import kiwi.components.DragObject;
	import kiwi.components.IDropTarget;
	import kiwi.display.DisplayObject;
	import kiwi.utils.Validation;
	
	import plantool.core.CoreModule;
	import plantool.data.QueryErrorResult;
	import plantool.data.QueryResult;
	import plantool.events.DateEvent;
	import plantool.events.UserEvent;
	import plantool.events.UserHourEvent;
	import plantool.helpers.ClassHelper;
	import plantool.modules.calendar.Calendar;
	import plantool.modules.users.data.User;
	import plantool.modules.users.data.UserHourData;
	import plantool.modules.users.data.UserHourTypes;
	import plantool.modules.users.display.IUserDay;
	import plantool.modules.users.display.IUserHour;
	import plantool.modules.users.display.IUserRow;
	import plantool.modules.users.display.UserDay;
	import plantool.modules.users.display.UserDisplayObject;
	import plantool.modules.users.display.UserHour;
	import plantool.modules.users.display.UserRow;
	import plantool.settings.Settings;
	
	//--------------------------------------------------------------------------
	//  [Metadata (Events, Style, Effects etc)]
	//--------------------------------------------------------------------------
	
	//--------------------------------------------------------------------------
	//  Class declaration
	//--------------------------------------------------------------------------
	public class Users extends CoreModule implements IDropTarget {
		//--------------------------------------------------------------------------
		//Class constants
		//--------------------------------------------------------------------------
		
		//--------------------------------------------------------------------------
		//Class variables
		//--------------------------------------------------------------------------
		private static var _instance:Users			= null;
		private var _isInitialized:Boolean			= false;
		private var _onCompleteHandler:Function		= null;
		private var _userDayClass:Class				= UserDay;
		private var _userHourClass:Class			= UserHour;
		private var _userRowClass:Class				= UserRow;
		private var _userRows:Vector.<IUserRow>		= new Vector.<IUserRow>();
		private var _userDays:Vector.<IUserDay>		= new Vector.<IUserDay>();
		private var _userHours:Vector.<IUserHour>	= new Vector.<IUserHour>();
		
		//--------------------------------------------------------------------------
		//Class properties
		//--------------------------------------------------------------------------
		public function get userDayClass():Class { return _userDayClass; }
		public function set userDayClass(value:Class):void {
			//validate class
			if (Validation.isSet(value) && ClassHelper.validateClass(value, IUserDay)) {
				_userDayClass = value;
			}
		}
		
		public function get userHourClass():Class { return _userHourClass; }
		public function set userHourClass(value:Class):void {
			//validate class
			if (Validation.isSet(value) && ClassHelper.validateClass(value, IUserHour)) {
				_userHourClass = value;
			}
		}
		
		public function get userRowClass():Class { return _userRowClass; }
		public function set userRowClass(value:Class):void {
			//validate class
			if (Validation.isSet(value) && ClassHelper.validateClass(value, IUserRow)) {
				_userRowClass = value;
			}
		}
		
		//--------------------------------------------------------------------------
		//Constructor and Destructor
		//--------------------------------------------------------------------------
		public function Users(pvt:PrivateConstructorClass) {
			super();
		}
		
		//--------------------------------------------------------------------------
		//Class methods
		//--------------------------------------------------------------------------
		public static function getInstance():Users {
			//check if instance exists, if not create one
			if (!Validation.isSet(_instance)) {
				_instance = new Users(new PrivateConstructorClass());
			}
			
			//return instance
			return _instance;
		}
		
		private function createDaysForUser(user:User):void {
			//get userrow
			var row:IUserRow = getUserRow(user);
			
			//create days
			var dateList:Vector.<Date> = dateApi.getVisibleDates();
			//dateApi.daysPerWeek
			for (var i:uint = 0; i < dateList.length; i++) {
				//create day
				var day:IUserDay 	= new userDayClass();
				day.user			= user;
				day.date			= new Date(dateList[i]);
				
				//create hours
				var hourDate:Date = new Date(dateList[i]);
				for (var j:uint = 0; j < dateApi.hoursPerDay; j++) {
					//create hour
					var hour:IUserHour 	= new userHourClass();
					hour.user			= day.user;
					hour.date		 	= new Date(hourDate);
					
					//store hour
					_userHours.push(hour);
					
					//add hour
					day.addHour(hour);
					
					//increase hour
					hourDate 		= new Date(hourDate);
					hourDate.hours 	+= 1;
				}
				
				//load user hours
				var from:Date 	= new Date(dateList[i]);
				var till:Date 	= new Date(dateList[i]);
				till.hours		+= dateApi.hoursPerDay;
				userApi.getUserHours(user.id, from, till, UserHourTypes.ALL, onComplete_getUserHours, onError_query);
				
				//store userday
				_userDays.push(day);
				
				//add day to userrow
				row.addDay(day);
			}
		}
		
		private function getHoursByData(userid:uint, from:Date, till:Date):Vector.<IUserHour> {
			//create filter function to filter results
			var filter:Function = function(item:IUserHour, idx:uint, list:Vector.<IUserHour>):Boolean {
				return (item.user.id == userid && item.date.valueOf() >= from.valueOf() && item.date.valueOf() < till.valueOf());
			}
			
			//filter the list
			var list:Vector.<IUserHour> = _userHours.filter(filter);
				
			//sort the days by date
			var sort:Function = function(itemA:IUserHour, itemB:IUserHour):Number {
				if (itemA.date.valueOf() < itemB.date.valueOf()) return -1;
				if (itemA.date.valueOf() > itemB.date.valueOf()) return 1;
				return 0;
			}
			list.sort(sort);
				
			//return list of hours
			return list;
		}
		
		private function initialized():void {
			//set initialized flag
			_isInitialized = true;
			
			//make this module visible
			Tweener.addTween(this, { _autoAlpha:1 });
			
			//database intact, call onCompleteHandler
			if (Validation.isSet(_onCompleteHandler)) {
				_onCompleteHandler();
			}
		}
		
		private function onComplete_getUsers(result:QueryResult):void {
			//show users
			if (Validation.isSet(result.data) && result.data.length > 0) {
				//create a row for each user
				for (var i:uint = 0; i < result.data.length; i++) {
					showUser(result.data[i]);
				}
			}
			
			//call initialized
			if (!_isInitialized) {
				initialized();
			}
		}
		
		private function onComplete_getUserHours(result:QueryResult):void {
			//get updated hours
			if (Validation.isSet(result.userdata[0]) && Validation.isSet(result.userdata[1]) && Validation.isSet(result.userdata[2])) {
				var hoursToUpdate:Vector.<IUserHour> = getHoursByData(result.userdata[0], result.userdata[1], result.userdata[2]);
				//reset all hours
				for (var k:uint = 0; k< hoursToUpdate.length; k++) {
					hoursToUpdate[k].hourData = null;
				}
			}
			
			//validate userdata as UserDa
			if (Validation.isSet(result.data) && result.data.length > 0) {
				for (var i:uint = 0; i < result.data.length; i++) {
					//validate result
					if (result.data[i] is UserHourData) {
						//cast result
						var hourData:UserHourData = result.data[i] as UserHourData;
						
						//get hours for result
						var hours:Vector.<IUserHour> = getHoursByData(hourData.user_id, hourData.fromDate, hourData.tillDate);
						
						//update all hours
						for (var j:uint = 0; j < hours.length; j++) {
							hours[j].hourData = hourData;
						}
					}
				}
			}
		}
		
		private function onComplete_saveUser(result:QueryResult):void {
		}
		
		private function onDate_change(e:DateEvent):void {
			//get all user-hours
			for each (var userRow:IUserRow in _userRows) {
				//get all userdays
				var days:Vector.<IUserDay> 	= getUserDaysByID(userRow.user.id);
				
				//get a list of all visible date's
				var dateList:Vector.<Date> 	= dateApi.getVisibleDates();
				
				//first get all current hours per day. if we update the dates on the hours, we will get double dates
				var hourLists:Array			= new Array();
				for (var i:uint = 0; i < dateList.length; i++) {
					//get hours
					var oldFromDate:Date	= new Date(days[i].date);
					var oldToDate:Date 		= new Date(oldFromDate);
					oldToDate.hours			+= dateApi.hoursPerDay;
					var hours:Vector.<IUserHour> = getHoursByData(userRow.user.id, oldFromDate, oldToDate);
					
					//store the hours
					hourLists.push(hours);
				}
				
				//update date's on all hours
				for (i = 0; i < dateList.length; i++) {
					//create date for hours
					var hourDate:Date	= new Date(dateList[i]);
					for (var j:uint = 0; j < hourLists[i].length; j++) {
						//set new date
						hourLists[i][j].date 		= new Date(hourDate);
						hourLists[i][j].hourData 	= null;
						
						//increase hours by 1
						hourDate 		= new Date(hourDate);
						hourDate.hours 	+= 1;
					}
					
					//set new date for day
					days[i].date = new Date(dateList[i]);
				}
			}
			
			var toDate:Date = new Date(dateList[dateList.length -1]);
			toDate.hours	+= dateApi.hoursPerDay;
			userApi.getAllUserHours(dateList[0], toDate, UserHourTypes.ALL, onComplete_getUserHours, onError_query);
		}
		
		private function onError_query(result:QueryErrorResult):void {
			trace("Error in query: ", result.query);
			trace(" - ", result.message);
		}
		
		private function onResize_stage(e:Event):void {
			this.x	= parseInt(settings.getSetting(Settings.SETTING_GLOBAL_MARGIN).value);
			this.y	= Calendar.getInstance().y + Calendar.getInstance().height + parseInt(settings.getSetting(Settings.SETTING_GLOBAL_MARGIN).value);
		}
		
		private function onUser_updated(e:UserEvent):void {
			//get updated user
			var user:User = userApi.getUserById(e.userid);
			
			//show new user
			if (Validation.isSet(user)) {
				showUser(user);
			} else {
				removeUser(e.userid);
			}
		}
		
		private function onUserHours_updated(e:UserHourEvent):void {
			//load new userhours
			userApi.getUserHours(e.userid, e.from, e.till, UserHourTypes.ALL, onComplete_getUserHours, onError_query);
		}
		
		private function removeUser(uid:uint):void {
			//remove hours
			var endDate:Date 	= dateApi.currentDate;
			endDate.date		+= dateApi.daysPerWeek;
			var hours:Vector.<IUserHour> = getHoursByData(uid, dateApi.getStartDate(), endDate);
			for each (var hour:IUserHour in hours) {
				_userHours.splice(_userHours.indexOf(hour), 1);
				hour.dispose();
			}
			
			//remove days
			var days:Vector.<IUserDay> = getUserDaysByID(uid);
			for each (var day:IUserDay in days) {
				_userDays.splice(_userDays.indexOf(day), 1);
				day.dispose();
			}
			
			//dispose the row for the user
			var row:IUserRow = getUserRowByID(uid);
			_userRows.splice(_userRows.indexOf(row), 1);
			row.dispose();
			row = null;
			
			//sort users
			sortUserRows();
			
			//position row
			_userRows.forEach(positionRow);
			
			//invalidate this module
			invalidate();
		}
		
		private function showUser(user:User):void {
			var row:IUserRow = getUserRowByID(user.id);
			if (Validation.isSet(row)) {
				//update user row
				row.user	= user;
				
				//update hours
				var endDate:Date 	= dateApi.currentDate;
				endDate.date		+= dateApi.daysPerWeek;
				var hours:Vector.<IUserHour> = getHoursByData(user.id, dateApi.getStartDate(), endDate);
				for each (var hour:IUserHour in hours) {
					hour.user = user;
				}
			} else {
				//create the userrow
				row 		= new userRowClass();
				row.user	= user;
				
				//store user
				_userRows.push(row);
				
				//create days for user
				createDaysForUser(user);
				
				//add the row to the stage
				addUser(row);
				
				//update background
				drawBackground();
			}
			
			//sort users
			sortUserRows();
			
			//position row
			_userRows.forEach(positionRow);
			
			//invalidate this module
			invalidate();
		}
		
		private function sortUserRows():void {
			//create sorting function
			var sort:Function = function(a:IUserRow, b:IUserRow):Number {
				if (a.user.sort < b.user.sort) return -1;
				else if (a.user.sort > b.user.sort) return 1;
				return 0;
			}
			
			//sort the users
			_userRows.sort(sort);
		}
		
		protected function addUser(row:IUserRow):void {
			//add rows
			addChild(row as DisplayObject);
		}
		
		protected function drawBackground():void {
			graphics.clear();
			graphics.beginFill(0xFFFFFF);
			graphics.drawRect(0, 0, (_userRows[0] as DisplayObject).width, (_userRows[0] as DisplayObject).height * _userRows.length);
			graphics.endFill();
		}
		
		protected function getUserDaysByID(id:uint):Vector.<IUserDay> {
			//filterfunction for userrows
			var filter:Function = function(item:IUserDay, idx:uint, list:Vector.<IUserDay>):Boolean {
				return item.user.id == id;
			}
			
			//filter the list
			var list:Vector.<IUserDay> = _userDays.filter(filter);
			
			//sort the days by date
			var sort:Function = function(itemA:IUserDay, itemB:IUserDay):Number {
				if (itemA.date.valueOf() < itemB.date.valueOf()) return -1;
				if (itemA.date.valueOf() > itemB.date.valueOf()) return 1;
				return 0;
			}
			list.sort(sort);
			
			//check if result is found
			if (list.length > 0) {
				return list;
			}
			
			//nothing found
			return null;
		}
		
		protected function getUserRowByID(id:uint):IUserRow {
			//filterfunction for userrows
			var filter:Function = function(item:IUserRow, idx:uint, list:Vector.<IUserRow>):Boolean {
				return item.user.id == id;
			}
			
			//filter the list
			var list:Vector.<IUserRow> = _userRows.filter(filter);
			//check if result is found
			if (list.length > 0) {
				return list[0];
			}
			
			//nothing found
			return null;
		}
		
		protected function getUserRow(user:User):IUserRow {
			//filterfunction for userrows
			var filter:Function = function(item:IUserRow, idx:uint, list:Vector.<IUserRow>):Boolean {
				return item.user == user;
			}
				
			//filter the list
			var list:Vector.<IUserRow> = _userRows.filter(filter);
			//check if result is found
			if (list.length > 0) {
				return list[0];
			}
			
			//nothing found
			return null;
		}
		
		protected override function init():void {
			//init events
			userApi.addEventListener(UserHourEvent.USER_HOUR_UPDATED		, onUserHours_updated);
			userApi.addEventListener(UserEvent.USER_UPDATED					, onUser_updated);
			
			//listen to page invalidate to position this module
			base.stage.addEventListener(Event.RESIZE		, onResize_stage);
			
			//listen to date-change
			dateApi.addEventListener(DateEvent.DATE_CHANGED		, onDate_change);
			
			//get all users
			userApi.getUsers(onComplete_getUsers, onError_query);
		}
		
		public function updateRowSorting(target:IUserRow):void {
			//calculate row index based by Y
			var row:UserDisplayObject 	= target as UserDisplayObject;
			var rowSortIndex:uint		= Math.min(Math.max(Math.floor((row.y + row.height / 2) / row.height), 0), _userRows.length -1);
			var oldIndex:uint			= _userRows.indexOf(target);
			
			//only update if row is different
			if (rowSortIndex != _userRows.indexOf(row as IUserRow)) {
				//switch sorting with current row
				row.user.sort			= rowSortIndex;
				
				//update all rows
				var i:uint;
				if (rowSortIndex > oldIndex) {
					for (i = rowSortIndex; i > oldIndex; i--) {
						_userRows[i].user.sort = i - 1;
					}
				} else if (rowSortIndex < oldIndex) {
					for (i = rowSortIndex; i < oldIndex; i++) {
						_userRows[i].user.sort = i + 1;
					}
				}
				
				//sort users
				sortUserRows();
				
				//update all row sortings
				for (i = 0; i < _userRows.length; i++) {
					_userRows[i].user.sort = i;
					
					//position row
					if (_userRows[i] != target) {
						positionRow(_userRows[i], i, _userRows);
					}
				}
			}
		}
		
		public function positionRow(row:IUserRow, index:int = -1, list:Vector.<IUserRow> = null, time:Number = 0.25):void {
			//position row
			if (index == -1) {
				index = _userRows.indexOf(row);
			}
			Tweener.addTween(row, { y:index * (row as DisplayObject).height, x:0, time:time });
		}
		
		public function saveUsers():void {
			//update all row sortings
			for (var i:uint = 0; i < _userRows.length; i++) {
				userApi.saveUser(_userRows[i].user, onComplete_saveUser, onError_query);
			}
		}
		
		public override function show(onCompleteHandler:Function=null):void {
			//check if we're initialized
			if (!_isInitialized) {
				//store oncompletehandler
				_onCompleteHandler	= onCompleteHandler;
			} else {
				//call oncompletehandler
				if (Validation.isSet(onCompleteHandler)) {
					onCompleteHandler();
				}
			}
		}
		
		public function dragObjectOut(target:DragObject):void {
			
		}
		
		public function dragObjectOver(target:DragObject):void {
		}
	}
}

//--------------------------------------------------------------------------
//END OF PACKAGE
//Define the PrivateConstructorClass here so an instance cannot be by anyone
//except for the class defined in the above package
//--------------------------------------------------------------------------
class PrivateConstructorClass {
	//--------------------------------------------------------------------------
	//Constructor and Destructor
	//--------------------------------------------------------------------------
	public function PrivateConstructorClass() {
	}
}